namespace Warehouse

open FSharp.Windows
open System.Collections.ObjectModel
open System.Data
open System.Windows
open System
open System.IO
open System.Configuration
open System.Linq
open Microsoft.FSharp.Reflection
open Monsoon

type ProductController(?repository) = 
    inherit Controller<ProductEvents, ProductModel>()

    let repository = defaultArg repository (new DataModel.Repository())

    let createUpc() = 
        let upc : UpcModel = Model.Create()
        upc.Note <- repository.NewPackage
        upc.Code <- repository.GenerateUpc()
        upc

    override this.InitModel model =
        if model.Brands = null then model.Brands <- repository.Brands |> Seq.toArray |> Array.sortBy(fun x -> x.Name)
        if model.UnitLookup = null then model.UnitLookup <- repository.Units.ToArray()
        if model.UpcNotes = null then model.UpcNotes <- repository.UpcNotes.ToArray()
        if model.UPCs = null || Seq.isEmpty model.UPCs
        then 
            assert model.IsNewEntity
            model.UPCs <- createUpc() |> ObservableCollection.singleton
        if model.IsNewEntity 
        then 
            model.Unit <- repository.Oz

    override this.Dispatcher = Sync << function 
        | Save ok -> this.Save ok
        | SizeOrColorChanging(newValue, cancel) -> this.SizeOrColorChanging newValue cancel
        | AddUpc -> this.AddUpc
        | CalculateShippingCost -> this.CalculateShippingCost
        | CalculateMinRetailCost -> this.CalculateMinRetailCost
        | UnitChanged -> this.UnitChanged
        | SetAsSKU -> this.SetAsSKU

    member this.Save ok model = 
        this.ValidateBeforeSave model
        if model.IsValid then
            if model.ProductName = null 
            then
                model.ProductName <- DataModel.ProductName(Name = model.SelectedProductName.TrimEnd(), Brand = model.SelectedBrand)
                //repository.AddToProductNames model.ProductName

            for upc in model.UPCs do upc.Code <- upc.Code.Trim()

            if model.IsNewEntity then
                assert (model.SKU = null)
                model.SKU <- model.UPCs.[0].Code

            if model.ShippingCost = 0M 
            then
                this.CalculateShippingCost model

            let monsoonAction = 
                if model.IsNewEntity
                then 
                    fun() -> this.MonsoonNewProduct model
                elif model.ShopCost <> model.Entity.ShopCost
                then
                    fun() ->
                        Monsoon.priceChange
                            {
                                MonsoonOP = 'u'
                                SKU = model.SKU
                                ShopCost = model.ShopCost
                                ExtendedPrice = model.DefaultBuy
                                SalePrice = model.DefaultSell
                                Shipping = model.ShippingCost
                                CostOfGoods = model.MinRetailCost
                            }
                elif model.SKU <> model.Entity.SKU 
                then
                    let oldSku, newSku = model.Entity.SKU, model.SKU
                    fun() -> 
                        this.MonsoonNewProduct model
                        Monsoon.quantityChange
                            [|
                                { UserData5 = lazy "Separate"; MonsoonOP = 'd'; SKU = oldSku; Quantity = model.TotalQuantity }
                                { UserData5 = lazy "Separate"; MonsoonOP = 'a'; SKU = newSku; Quantity = model.TotalQuantity }
                            |]
                        
                else
                    ignore

            model.UpdateEntity()
            model.SaveEntity repository
            model.Compute()

            if model.MinRetailCost = 0M
            then
                this.CalculateMinRetailCost model
            model.UpdateEntity()
            model.SaveEntity repository

            monsoonAction()
            ok()

    member private this.MonsoonNewProduct model = 
        let nonStandardProduct = model.SKU.Length <= 11
        Monsoon.newProduct
            {
                MonsoonOP = 'a'
                Category = "Beauty"
                Classification = model.ProductName.Brand.Name + ":" + model.Classification
                Brand = model.ProductName.Brand.Name
                Size = model.DisplaySize
                Title = model.Title

                SKU = model.SKU
                EAN = if model.SKU.Length = 13 then model.SKU else null
                UPC = if model.SKU.Length = 12 then model.SKU else null
                NonStandard = if nonStandardProduct then "Y" else "N" 

                ShopCost = model.ShopCost
                ExtendedPrice = model.DefaultBuy
                SalePrice = model.DefaultSell
                Shipping = model.ShippingCost
                CostOfGoods = model.MinRetailCost
                Price = if nonStandardProduct then Nullable(model.MinRetailCost * 1.3M) else Nullable()
            }

    member this.ValidateBeforeSave model =
        model.ClearAllErrors()
        model |> Validation.objectRequired  <@ fun m -> m.SelectedBrand @>
        model |> Validation.objectRequired <@ fun m -> m.SelectedProductName @>
        model |> Validation.positive <@ fun m -> m.ShopCost @>

        assert (model.Unit <> null)
        if model.Unit.IsGallon() then model.SizeOrColor <- "1"
        if model.Unit.IsSizeOrColorRequired() && String.IsNullOrEmpty model.SizeOrColor
        then
            model |> Validation.setError <@ fun m -> m.SizeOrColor @> "Size/color is mandatory field."

        if not(model.Unit.IsOz() || model.Unit.IsGallon())
        then 
            model |> Validation.positive <@ fun m -> m.ShippingCost @>

        if model.IsNewEntity 
            && model.MinRetailCost <> 0M 
            && model.MinRetailCost < model.MinRetailCostByFormula
        then
            model |> Validation.setError <@ fun m -> m.MinRetailCost @> (sprintf "Min retails cost cannot go below %M for new products." model.MinRetailCostByFormula)

        for upc in model.UPCs do
            upc |> Validation.textRequired <@ fun m -> m.Code @>
            upc |> Validation.objectRequired <@ fun m -> m.Note @>
            if upc.IsNewEntity 
                && repository.UpcExists upc.Code then
                let message = sprintf "Code %s already exists in database" upc.Code
                upc |> Validation.setError <@ fun m -> m.Code @> message

        if model.SKU <> null && not(model.UPCs |> Seq.exists(fun upc -> upc.Code = model.SKU)) 
        then
            model |> Validation.setError <@ fun m -> m.SKU @> "SKU has to be equal to one of UPC codes."

    member this.SizeOrColorChanging newValue cancel model =
        if model.Unit.Name.ToLower() <> "color" && not (Decimal.canBeParsedFrom newValue) 
        then
            cancel()

    member this.AddUpc model = 
        createUpc() |> model.UPCs.Add 

    member this.CalculateShippingCost model = 
        if model.IsCalculateShippingCostEnable
        then
            model.ShippingCost <-
                if model.Unit.IsGallon() then 11.00M
                else
                    match decimal model.SizeOrColor with
                    | LessThan 3M -> 2.00M
                    | LessThan 5M -> 2.50M
                    | LessThan 7M -> 3.00M
                    | LessThan 11.5M -> 3.50M
                    | LessThan 34.1M -> 6.00M
                    | x -> model.ShippingCost

    member this.CalculateMinRetailCost model = 
        if not model.IsNewEntity
        then 
            model.Compute() 
            model.SetMinRetailCostByFormula()

    member this.UnitChanged model =
        if not(model.Unit.IsSizeOrColorRequired()) then model.SizeOrColor <- null

    member this.SetAsSKU model = 
        model.SKU <- model.SelectedUpc.Code

